//! Live in-memory state for a simulation task.

use std::mem;

use craftdig_datamodel::block::{self, BlkId, BlockStateSnapshot};
use craftdig_datamodel::chunk::BlockMap;
use craftdig_datamodel::entity::{EntityId, EntityState};
use craftdig_datamodel::message::{MessageContainer, MsgTyId};
use craftdig_datamodel::player::id::PlayerId;
use craftdig_datamodel::world::{editable, history, WorldState, WorldView};
use craftdig_math::BlkCoord;

use crate::msg_scheduler::{MsgScheduler, TickSched};

use super::update::{BlockChange, EntityUpdate, EntityUpdateData};

/// State kept for a running game simulation.
pub struct SimState {
    /// The tick that we're currently processing, or which we last fully
    /// processed if not yet processing the next tick.
    cur_tick: u64,

    /// All players that have entities in the world.
    current_players: Vec<PlayerId>,

    /// Recent world history.
    history: history::History,

    /// Message scheduler.
    msg_sched: MsgScheduler,

    /// State we're applying starting the next tick with.
    next_tick: NextTickState,
}

impl SimState {
    pub fn new(history: history::History) -> Self {
        Self {
            cur_tick: history.newest_tick(),
            history,
            current_players: Vec::new(),
            msg_sched: MsgScheduler::new_empty(),
            next_tick: NextTickState::new_empty(),
        }
    }

    pub fn cur_tick(&self) -> u64 {
        self.cur_tick
    }

    /// Increments the current tick, returning it.
    ///
    /// This might also do other basic cleanup actions in the future.
    pub fn increment_tick(&mut self) -> u64 {
        self.cur_tick += 1;
        self.cur_tick
    }

    pub fn history(&self) -> &history::History {
        &self.history
    }

    pub fn history_mut(&mut self) -> &mut history::History {
        &mut self.history
    }

    pub fn players(&self) -> &[PlayerId] {
        &self.current_players
    }

    pub fn has_player(&self, id: &PlayerId) -> bool {
        self.current_players.contains(id)
    }

    pub fn add_player(&mut self, id: PlayerId) {
        if self.has_player(&id) {
            panic!("sim: tried to re-add player");
        }

        self.current_players.push(id);
    }

    pub fn msg_sched(&self) -> &MsgScheduler {
        &self.msg_sched
    }

    pub fn msg_sched_mut(&mut self) -> &mut MsgScheduler {
        &mut self.msg_sched
    }

    /// Takes the next tick state and resets it.
    pub fn take_next_tick(&mut self) -> NextTickState {
        let mut state = NextTickState::new_empty();
        mem::swap(&mut state, &mut self.next_tick);
        state
    }
}

/// Tick-local state for updating the world.
pub struct TickState<'s> {
    /// The tick that we're currently processing.
    processing_tick: u64,

    /// Editable simulation state that we can schedule messages and stuff with.
    sim: &'s mut SimState,

    /// Editable world state that we write changes to.
    world: editable::EditableWorld<&'s WorldState>,

    /// Block updates that have been ordered.
    pending_block_updates: Vec<BlockChange>,

    /// Entity updates that have been ordered.
    pending_entity_changes: Vec<EntityUpdate>,
}

impl<'s> TickState<'s> {
    pub fn new(tick: u64, sim: &'s mut SimState, prev: &'s WorldState) -> Self {
        Self {
            processing_tick: tick,
            sim,
            world: editable::EditableWorld::new(prev),
            pending_block_updates: Vec::new(),
            pending_entity_changes: Vec::new(),
        }
    }

    pub fn tick(&self) -> u64 {
        self.processing_tick
    }

    pub fn players(&self) -> &[PlayerId] {
        self.sim.players()
    }

    /// Returns a reference to the in-flight world state.
    pub fn world(&self) -> &editable::EditableWorld<&'s WorldState> {
        &self.world
    }

    /// Returns a mutable reference to the in-flight world state.
    pub fn world_mut(&mut self) -> &mut editable::EditableWorld<&'s WorldState> {
        &mut self.world
    }

    pub fn get_block_id(&self, coord: BlkCoord) -> Option<BlkId> {
        self.world.get_block_id(coord)
    }

    pub fn get_entity_state(&self, eid: &EntityId) -> Option<&EntityState> {
        self.world.get_entity_state(eid)
    }

    /// Marks a block at a position to be changed.  This will be carried out
    /// either later in this tick or in the next tick, depending on which phase
    /// of the tick we're currently in.
    pub fn queue_set_block_state(&mut self, target: BlkCoord, state: &BlockStateSnapshot) {
        let update = BlockChange::new_snapshot(target, state.clone());
        self.pending_block_updates.push(update);
    }

    /// Adds an entry to change something about a particular entity, such as
    /// updating its position or giving it a nudge.
    pub fn queue_entity_change(&mut self, target: EntityId, data: EntityUpdateData) {
        let change = EntityUpdate::new(target, data);
        self.pending_entity_changes.push(change);
    }

    /// Clears all pending block updates.
    pub fn clear_pending_block_updates(&mut self) {
        let l = self.pending_block_updates.len();
        eprintln!("craftdig: clearing {l} block updates");
        self.pending_block_updates.clear();
    }

    /// Queue a message for processing in this tick.
    // FIXME this is inelegant, it should be able to figure out if it goes
    // directly into the next tick
    pub fn queue_immediate_msg(&mut self, target: BlkCoord, mc: MessageContainer) {
        let sched = self.sim.msg_sched_mut();
        sched.schedule_msg(self.processing_tick, target, mc);
    }

    /// Queue a message to be delivered on the next tick.
    pub fn queue_delayed_msg(&mut self, target: BlkCoord, mc: MessageContainer) {
        self.queue_future_msg(target, 1, mc);
    }

    /// Queue a message to be delivered at a future tick.
    pub fn queue_future_msg(&mut self, target: BlkCoord, delay: u64, mc: MessageContainer) {
        assert!(delay > 0, "sim: future message delay must be nonzero");
        let sched = self.sim.msg_sched_mut();
        let ftick = self.processing_tick + delay;
        sched.schedule_msg(ftick, target, mc);
    }

    /// Returns a blockmap of messages for the current tick.
    // FIXME this is inelegant, we might have stale messages
    pub fn take_cur_tick_messages(&mut self, mty: MsgTyId) -> BlockMap<Vec<MessageContainer>> {
        let sched = self.sim.msg_sched_mut();
        let tsched = sched.take_tick_msgs(self.processing_tick, mty);
        tsched
    }

    /// Takes the `TickSched` for the current tick out of the msg scheduler to
    /// clean up any stale state.
    pub fn take_all_cur_tick_messages(&mut self) -> Option<TickSched> {
        let sched = self.sim.msg_sched_mut();
        sched.remove_tick(self.processing_tick)
    }

    /// Takes the block changes and resets the list.
    pub fn take_block_changes(&mut self) -> Vec<BlockChange> {
        let mut chgs = Vec::new();
        mem::swap(&mut chgs, &mut self.pending_block_updates);
        chgs
    }

    /// Takes the entity changes and resets the list.
    pub fn take_entity_updates(&mut self) -> Vec<EntityUpdate> {
        let mut chgs = Vec::new();
        mem::swap(&mut chgs, &mut self.pending_entity_changes);
        chgs
    }

    /// Takes any internal tick state and submits it down as a new instance of
    /// the next tick state in the underlying sim state, cleaning up some
    /// internal state.
    ///
    /// Should NOT be called unless we're getting ready to finish working the
    /// tick.
    pub fn finalize_next_tick_state(&mut self) {
        let msgs = self
            .take_all_cur_tick_messages()
            .unwrap_or_else(TickSched::new_empty);
        let block_chgs = self.take_block_changes();

        eprintln!(
            "craftdig: finalizing next tick state (nblocks {}, nmsgs {})",
            block_chgs.len(),
            -1
        );

        self.sim.next_tick = NextTickState::new(msgs, block_chgs);
    }

    pub fn into_world_diff(self) -> editable::Diff {
        self.world.into_diff()
    }
}

/// State we're carrying forwards into the next tick.
#[derive(Clone)]
pub struct NextTickState {
    /// Dangling tick msg state.
    // TODO this doesn't feel like the right type to use here
    dangling_tick_sched: TickSched,

    /// Block changes to make, usually from handling late messages.
    block_changes: Vec<BlockChange>,
}

impl NextTickState {
    pub fn new(dangling_tick_sched: TickSched, block_changes: Vec<BlockChange>) -> Self {
        Self {
            dangling_tick_sched,
            block_changes,
        }
    }

    pub fn new_empty() -> Self {
        Self::new(TickSched::new_empty(), Vec::new())
    }

    pub fn msgs(&self) -> &TickSched {
        &self.dangling_tick_sched
    }

    pub fn block_changes(&self) -> &[BlockChange] {
        &self.block_changes
    }
}
